// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "content/browser/appcache/appcache_response.h"

#include <stddef.h>

#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/compiler_specific.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/numerics/safe_math.h"
#include "base/pickle.h"
#include "base/single_thread_task_runner.h"
#include "base/strings/string_util.h"
#include "base/threading/thread_task_runner_handle.h"
#include "content/browser/appcache/appcache_storage.h"
#include "net/base/completion_callback.h"
#include "net/base/io_buffer.h"
#include "net/base/net_errors.h"

namespace content {

namespace {

    // Disk cache entry data indices.
    enum { kResponseInfoIndex,
        kResponseContentIndex,
        kResponseMetadataIndex };

    // An IOBuffer that wraps a pickle's data. Ownership of the
    // pickle is transfered to the WrappedPickleIOBuffer object.
    class WrappedPickleIOBuffer : public net::WrappedIOBuffer {
    public:
        explicit WrappedPickleIOBuffer(const base::Pickle* pickle)
            : net::WrappedIOBuffer(reinterpret_cast<const char*>(pickle->data()))
            , pickle_(pickle)
        {
            DCHECK(pickle->data());
        }

    private:
        ~WrappedPickleIOBuffer() override { }

        std::unique_ptr<const base::Pickle> pickle_;
    };

} // anon namespace

// AppCacheResponseInfo ----------------------------------------------

AppCacheResponseInfo::AppCacheResponseInfo(AppCacheStorage* storage,
    const GURL& manifest_url,
    int64_t response_id,
    net::HttpResponseInfo* http_info,
    int64_t response_data_size)
    : manifest_url_(manifest_url)
    , response_id_(response_id)
    , http_response_info_(http_info)
    , response_data_size_(response_data_size)
    , storage_(storage)
{
    DCHECK(http_info);
    DCHECK(response_id != kAppCacheNoResponseId);
    storage_->working_set()->AddResponseInfo(this);
}

AppCacheResponseInfo::~AppCacheResponseInfo()
{
    storage_->working_set()->RemoveResponseInfo(this);
}

// HttpResponseInfoIOBuffer ------------------------------------------

HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer()
    : response_data_size(kUnkownResponseDataSize)
{
}

HttpResponseInfoIOBuffer::HttpResponseInfoIOBuffer(net::HttpResponseInfo* info)
    : http_info(info)
    , response_data_size(kUnkownResponseDataSize)
{
}

HttpResponseInfoIOBuffer::~HttpResponseInfoIOBuffer() { }

// AppCacheDiskCacheInterface ----------------------------------------

AppCacheDiskCacheInterface::AppCacheDiskCacheInterface()
    : weak_factory_(this)
{
}

base::WeakPtr<AppCacheDiskCacheInterface>
AppCacheDiskCacheInterface::GetWeakPtr()
{
    return weak_factory_.GetWeakPtr();
}

AppCacheDiskCacheInterface::~AppCacheDiskCacheInterface() { }

// AppCacheResponseIO ----------------------------------------------

AppCacheResponseIO::AppCacheResponseIO(
    int64_t response_id,
    const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
    : response_id_(response_id)
    , disk_cache_(disk_cache)
    , entry_(NULL)
    , buffer_len_(0)
    , weak_factory_(this)
{
}

AppCacheResponseIO::~AppCacheResponseIO()
{
    if (entry_)
        entry_->Close();
}

void AppCacheResponseIO::ScheduleIOCompletionCallback(int result)
{
    base::ThreadTaskRunnerHandle::Get()->PostTask(
        FROM_HERE, base::Bind(&AppCacheResponseIO::OnIOComplete, weak_factory_.GetWeakPtr(), result));
}

void AppCacheResponseIO::InvokeUserCompletionCallback(int result)
{
    // Clear the user callback and buffers prior to invoking the callback
    // so the caller can schedule additional operations in the callback.
    buffer_ = NULL;
    info_buffer_ = NULL;
    net::CompletionCallback cb = callback_;
    callback_.Reset();
    cb.Run(result);
}

void AppCacheResponseIO::ReadRaw(int index, int offset,
    net::IOBuffer* buf, int buf_len)
{
    DCHECK(entry_);
    int rv = entry_->Read(
        index, offset, buf, buf_len,
        base::Bind(&AppCacheResponseIO::OnRawIOComplete,
            weak_factory_.GetWeakPtr()));
    if (rv != net::ERR_IO_PENDING)
        ScheduleIOCompletionCallback(rv);
}

void AppCacheResponseIO::WriteRaw(int index, int offset,
    net::IOBuffer* buf, int buf_len)
{
    DCHECK(entry_);
    int rv = entry_->Write(
        index, offset, buf, buf_len,
        base::Bind(&AppCacheResponseIO::OnRawIOComplete,
            weak_factory_.GetWeakPtr()));
    if (rv != net::ERR_IO_PENDING)
        ScheduleIOCompletionCallback(rv);
}

void AppCacheResponseIO::OnRawIOComplete(int result)
{
    DCHECK_NE(net::ERR_IO_PENDING, result);
    OnIOComplete(result);
}

void AppCacheResponseIO::OpenEntryIfNeeded()
{
    int rv;
    AppCacheDiskCacheInterface::Entry** entry_ptr = NULL;
    if (entry_) {
        rv = net::OK;
    } else if (!disk_cache_) {
        rv = net::ERR_FAILED;
    } else {
        entry_ptr = new AppCacheDiskCacheInterface::Entry*;
        open_callback_ = base::Bind(&AppCacheResponseIO::OpenEntryCallback,
            weak_factory_.GetWeakPtr(), base::Owned(entry_ptr));
        rv = disk_cache_->OpenEntry(response_id_, entry_ptr, open_callback_);
    }

    if (rv != net::ERR_IO_PENDING)
        OpenEntryCallback(entry_ptr, rv);
}

void AppCacheResponseIO::OpenEntryCallback(
    AppCacheDiskCacheInterface::Entry** entry, int rv)
{
    DCHECK(info_buffer_.get() || buffer_.get());

    if (!open_callback_.is_null()) {
        if (rv == net::OK) {
            DCHECK(entry);
            entry_ = *entry;
        }
        open_callback_.Reset();
    }
    OnOpenEntryComplete();
}

// AppCacheResponseReader ----------------------------------------------

AppCacheResponseReader::AppCacheResponseReader(
    int64_t response_id,
    const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
    : AppCacheResponseIO(response_id, disk_cache)
    , range_offset_(0)
    , range_length_(std::numeric_limits<int32_t>::max())
    , read_position_(0)
    , reading_metadata_size_(0)
    , weak_factory_(this)
{
}

AppCacheResponseReader::~AppCacheResponseReader()
{
}

void AppCacheResponseReader::ReadInfo(HttpResponseInfoIOBuffer* info_buf,
    const net::CompletionCallback& callback)
{
    DCHECK(!callback.is_null());
    DCHECK(!IsReadPending());
    DCHECK(info_buf);
    DCHECK(!info_buf->http_info.get());
    DCHECK(!buffer_.get());
    DCHECK(!info_buffer_.get());

    info_buffer_ = info_buf;
    callback_ = callback; // cleared on completion
    OpenEntryIfNeeded();
}

void AppCacheResponseReader::ContinueReadInfo()
{
    int size = entry_->GetSize(kResponseInfoIndex);
    if (size <= 0) {
        ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
        return;
    }

    buffer_ = new net::IOBuffer(size);
    ReadRaw(kResponseInfoIndex, 0, buffer_.get(), size);
}

void AppCacheResponseReader::ReadData(net::IOBuffer* buf, int buf_len,
    const net::CompletionCallback& callback)
{
    DCHECK(!callback.is_null());
    DCHECK(!IsReadPending());
    DCHECK(buf);
    DCHECK(buf_len >= 0);
    DCHECK(!buffer_.get());
    DCHECK(!info_buffer_.get());

    buffer_ = buf;
    buffer_len_ = buf_len;
    callback_ = callback; // cleared on completion
    OpenEntryIfNeeded();
}

void AppCacheResponseReader::ContinueReadData()
{
    if (read_position_ + buffer_len_ > range_length_) {
        // TODO(michaeln): What about integer overflows?
        DCHECK(range_length_ >= read_position_);
        buffer_len_ = range_length_ - read_position_;
    }
    ReadRaw(kResponseContentIndex,
        range_offset_ + read_position_,
        buffer_.get(),
        buffer_len_);
}

void AppCacheResponseReader::SetReadRange(int offset, int length)
{
    DCHECK(!IsReadPending() && !read_position_);
    range_offset_ = offset;
    range_length_ = length;
}

void AppCacheResponseReader::OnIOComplete(int result)
{
    if (result >= 0) {
        if (reading_metadata_size_) {
            DCHECK(reading_metadata_size_ == result);
            DCHECK(info_buffer_->http_info->metadata);
            reading_metadata_size_ = 0;
        } else if (info_buffer_.get()) {
            // Deserialize the http info structure, ensuring we got headers.
            base::Pickle pickle(buffer_->data(), result);
            std::unique_ptr<net::HttpResponseInfo> info(new net::HttpResponseInfo);
            bool response_truncated = false;
            if (!info->InitFromPickle(pickle, &response_truncated) || !info->headers.get()) {
                InvokeUserCompletionCallback(net::ERR_FAILED);
                return;
            }
            DCHECK(!response_truncated);
            info_buffer_->http_info.reset(info.release());

            // Also return the size of the response body
            DCHECK(entry_);
            info_buffer_->response_data_size = entry_->GetSize(kResponseContentIndex);

            int64_t metadata_size = entry_->GetSize(kResponseMetadataIndex);
            if (metadata_size > 0) {
                reading_metadata_size_ = metadata_size;
                info_buffer_->http_info->metadata = new net::IOBufferWithSize(
                    base::checked_cast<size_t>(metadata_size));
                ReadRaw(kResponseMetadataIndex, 0,
                    info_buffer_->http_info->metadata.get(), metadata_size);
                return;
            }
        } else {
            read_position_ += result;
        }
    }
    InvokeUserCompletionCallback(result);
}

void AppCacheResponseReader::OnOpenEntryComplete()
{
    if (!entry_) {
        ScheduleIOCompletionCallback(net::ERR_CACHE_MISS);
        return;
    }
    if (info_buffer_.get())
        ContinueReadInfo();
    else
        ContinueReadData();
}

// AppCacheResponseWriter ----------------------------------------------

AppCacheResponseWriter::AppCacheResponseWriter(
    int64_t response_id,
    const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
    : AppCacheResponseIO(response_id, disk_cache)
    , info_size_(0)
    , write_position_(0)
    , write_amount_(0)
    , creation_phase_(INITIAL_ATTEMPT)
    , weak_factory_(this)
{
}

AppCacheResponseWriter::~AppCacheResponseWriter()
{
}

void AppCacheResponseWriter::WriteInfo(
    HttpResponseInfoIOBuffer* info_buf,
    const net::CompletionCallback& callback)
{
    DCHECK(!callback.is_null());
    DCHECK(!IsWritePending());
    DCHECK(info_buf);
    DCHECK(info_buf->http_info.get());
    DCHECK(!buffer_.get());
    DCHECK(!info_buffer_.get());
    DCHECK(info_buf->http_info->headers.get());

    info_buffer_ = info_buf;
    callback_ = callback; // cleared on completion
    CreateEntryIfNeededAndContinue();
}

void AppCacheResponseWriter::ContinueWriteInfo()
{
    if (!entry_) {
        ScheduleIOCompletionCallback(net::ERR_FAILED);
        return;
    }

    const bool kSkipTransientHeaders = true;
    const bool kTruncated = false;
    base::Pickle* pickle = new base::Pickle;
    info_buffer_->http_info->Persist(pickle, kSkipTransientHeaders, kTruncated);
    write_amount_ = static_cast<int>(pickle->size());
    buffer_ = new WrappedPickleIOBuffer(pickle); // takes ownership of pickle
    WriteRaw(kResponseInfoIndex, 0, buffer_.get(), write_amount_);
}

void AppCacheResponseWriter::WriteData(
    net::IOBuffer* buf, int buf_len, const net::CompletionCallback& callback)
{
    DCHECK(!callback.is_null());
    DCHECK(!IsWritePending());
    DCHECK(buf);
    DCHECK(buf_len >= 0);
    DCHECK(!buffer_.get());
    DCHECK(!info_buffer_.get());

    buffer_ = buf;
    write_amount_ = buf_len;
    callback_ = callback; // cleared on completion
    CreateEntryIfNeededAndContinue();
}

void AppCacheResponseWriter::ContinueWriteData()
{
    if (!entry_) {
        ScheduleIOCompletionCallback(net::ERR_FAILED);
        return;
    }
    WriteRaw(
        kResponseContentIndex, write_position_, buffer_.get(), write_amount_);
}

void AppCacheResponseWriter::OnIOComplete(int result)
{
    if (result >= 0) {
        DCHECK(write_amount_ == result);
        if (!info_buffer_.get())
            write_position_ += result;
        else
            info_size_ = result;
    }
    InvokeUserCompletionCallback(result);
}

void AppCacheResponseWriter::CreateEntryIfNeededAndContinue()
{
    int rv;
    AppCacheDiskCacheInterface::Entry** entry_ptr = NULL;
    if (entry_) {
        creation_phase_ = NO_ATTEMPT;
        rv = net::OK;
    } else if (!disk_cache_) {
        creation_phase_ = NO_ATTEMPT;
        rv = net::ERR_FAILED;
    } else {
        creation_phase_ = INITIAL_ATTEMPT;
        entry_ptr = new AppCacheDiskCacheInterface::Entry*;
        create_callback_ = base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete,
            weak_factory_.GetWeakPtr(), base::Owned(entry_ptr));
        rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_);
    }
    if (rv != net::ERR_IO_PENDING)
        OnCreateEntryComplete(entry_ptr, rv);
}

void AppCacheResponseWriter::OnCreateEntryComplete(
    AppCacheDiskCacheInterface::Entry** entry, int rv)
{
    DCHECK(info_buffer_.get() || buffer_.get());

    if (!disk_cache_) {
        ScheduleIOCompletionCallback(net::ERR_FAILED);
        return;
    } else if (creation_phase_ == INITIAL_ATTEMPT) {
        if (rv != net::OK) {
            // We may try to overwrite existing entries.
            creation_phase_ = DOOM_EXISTING;
            rv = disk_cache_->DoomEntry(response_id_, create_callback_);
            if (rv != net::ERR_IO_PENDING)
                OnCreateEntryComplete(NULL, rv);
            return;
        }
    } else if (creation_phase_ == DOOM_EXISTING) {
        creation_phase_ = SECOND_ATTEMPT;
        AppCacheDiskCacheInterface::Entry** entry_ptr = new AppCacheDiskCacheInterface::Entry*;
        create_callback_ = base::Bind(&AppCacheResponseWriter::OnCreateEntryComplete,
            weak_factory_.GetWeakPtr(), base::Owned(entry_ptr));
        rv = disk_cache_->CreateEntry(response_id_, entry_ptr, create_callback_);
        if (rv != net::ERR_IO_PENDING)
            OnCreateEntryComplete(entry_ptr, rv);
        return;
    }

    if (!create_callback_.is_null()) {
        if (rv == net::OK)
            entry_ = *entry;

        create_callback_.Reset();
    }

    if (info_buffer_.get())
        ContinueWriteInfo();
    else
        ContinueWriteData();
}

// AppCacheResponseMetadataWriter ----------------------------------------------

AppCacheResponseMetadataWriter::AppCacheResponseMetadataWriter(
    int64_t response_id,
    const base::WeakPtr<AppCacheDiskCacheInterface>& disk_cache)
    : AppCacheResponseIO(response_id, disk_cache)
    , write_amount_(0)
    , weak_factory_(this)
{
}

AppCacheResponseMetadataWriter::~AppCacheResponseMetadataWriter()
{
}

void AppCacheResponseMetadataWriter::WriteMetadata(
    net::IOBuffer* buf,
    int buf_len,
    const net::CompletionCallback& callback)
{
    DCHECK(!callback.is_null());
    DCHECK(!IsIOPending());
    DCHECK(buf);
    DCHECK(buf_len >= 0);
    DCHECK(!buffer_.get());

    buffer_ = buf;
    write_amount_ = buf_len;
    callback_ = callback; // cleared on completion
    OpenEntryIfNeeded();
}

void AppCacheResponseMetadataWriter::OnOpenEntryComplete()
{
    if (!entry_) {
        ScheduleIOCompletionCallback(net::ERR_FAILED);
        return;
    }
    WriteRaw(kResponseMetadataIndex, 0, buffer_.get(), write_amount_);
}

void AppCacheResponseMetadataWriter::OnIOComplete(int result)
{
    DCHECK(result < 0 || write_amount_ == result);
    InvokeUserCompletionCallback(result);
}

} // namespace content
